﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data.SqlServerCe;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using CoreGallery.Addins.Contracts;
using CoreGallery.Core.AddinMgr;
using CoreGallery.Core.Data;

namespace CoreGallery.Core.AddinMgr
{
    public class AddinManager
    {
        private CoreGalleryIndex db = null;
        private Type[] knownInterfaces = { typeof(ITransformAddinBase), typeof(IAcquisitionAddinBase), typeof(IPublishingAddinBase), typeof(IServiceAddinBase), typeof(IStorageProviderBase) };
        private IList<AddinInfo> _loadedAddins = new List<AddinInfo>();

        private static object _lock = new object();
        private static AddinManager mgr;

        private AddinManager()
        {
            string sdfPath = ConfigurationManager.ConnectionStrings["CoreGallery.Core.Properties.Settings.CoreGalleryIndexConnectionString"].ConnectionString;

            db = new CoreGalleryIndex(sdfPath);

            if(System.Diagnostics.Debugger.IsAttached) db.Log = Console.Out;
        }

        public static AddinManager GetInstance()
        {
            lock(_lock)
            {
                if (mgr == null) mgr = new AddinManager();
            }

            return mgr;
        }

        public IList<AddinInfo> LoadedAddins
        {
            get { return _loadedAddins; }
        }

        public IEnumerable<AddinInfo> GetLoadedAddins<T>()
        {
            return _loadedAddins.Where(t => t.AddinInterface == typeof(T)).Select(t => t);
        }
        
        public void LoadAddIns()
        {
            UnloadAllAddIns();
            
            // Load app-bundled addins
            LoadAddIns(Path.Combine(Environment.CurrentDirectory, "Addins"));

            // Load global addins
            LoadAddIns(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "CoreGallery\\Addins"));

            // Load user-specific addins
            LoadAddIns(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "CoreGallery\\Addins"));
        }

        private void LoadAddIns(string location)
        {
            if (!Directory.Exists(location)) return;

            Application.Debug(" >> Processing addins in: {0}", location);

            foreach (string file in Directory.GetFiles(location, "*.dll"))
            {
                // You must supply a path to the assembly here
                Assembly currAssembly = Assembly.LoadFile(file);

                // Display all the types contained in the specified assembly.
                foreach (Type currType in currAssembly.GetExportedTypes())
                {
                    LoadAddinInfoFromType(currType);
                }
                try
                {
                    db.SubmitChanges();
                    Application.Debug("** Changes written");
                }
                catch (Exception e)
                {
                    Application.Debug(e.Message);
                }

            }

            foreach (string dir in Directory.GetDirectories(location))
            {
                LoadAddIns(dir);
            }
        }

        private AddinInfo LoadAddinInfoFromType(Type currType)
        {
            AddinInfo newAddinInfo = null;

            // Intersect creates a collection based on matching items in both collections
            Type typeOfInterest = knownInterfaces.Intersect(currType.GetInterfaces()).FirstOrDefault();

            // Any matching item means that this type has an interface we care about
            if (typeOfInterest != null)
            {
                Application.Debug("  >> Processing addin class {0}", currType.Name);

                // Get our custom attribute (assuming this is a valid addin)
                CoreAddinAttribute[] sa = (CoreAddinAttribute[])currType.GetCustomAttributes(typeof(CoreAddinAttribute), false);

                // This is a unique attribute that can only occur once
                if (sa.Length == 1)
                {
                    newAddinInfo = new AddinInfo();
                    newAddinInfo.AddinInterface = typeOfInterest;
                    newAddinInfo.AddinType = currType;

                    newAddinInfo.AddinName = sa[0].Name;
                    newAddinInfo.Description = sa[0].Description;
                    newAddinInfo.UniqueId = sa[0].UniqueKey;

                    // See if this addin is known and enabled (AddinStatus = 0)
                    var matchingAddin = (from x in db.CoreAddins
                                         where x.AddinGuid.Equals(sa[0].UniqueKey)
                                         select x).FirstOrDefault();

                    // Addin is enabled or is new
                    if (matchingAddin == null || matchingAddin.AddinStatus == 0)
                    {
                        newAddinInfo.IsEnabled = true;

                        // If this addin is new, insert the details to DB cache
                        if (matchingAddin == null)
                        {
                            db.CoreAddins.InsertOnSubmit(new CoreAddins()
                            {
                                AddinName = newAddinInfo.AddinInstance.Name,
                                AddinGuid = newAddinInfo.AddinInstance.UniqueKey
                            });
                        }
                    }
                    else if (matchingAddin.AddinStatus == 1) // Addin is known but not enabled
                    {
                        // TODO: Disabled addin should be added to list of addins for management but not instantiated/used
                        newAddinInfo.IsEnabled = false;
                    }
                    else if (matchingAddin.AddinStatus == 2) // Addin is complete disabled and should be ignored
                    {
                        newAddinInfo = null;
                    }

                    if (newAddinInfo != null)
                    {
                        _loadedAddins.Add(newAddinInfo);
                    }
                }
            }

            return newAddinInfo;
        }

        public void ActivateAddin(AddinInfo addinInfo)
        {
            if (!addinInfo.IsLoaded)
            {
                addinInfo.AddinInstance = (ICoreAddinBase)Activator.CreateInstance(addinInfo.AddinType);
                addinInfo.AddinInstance.OnLoad();
                addinInfo.IsLoaded = true;

                addinInfo.AddinInstance.OnInit();
            }
        }

        public void DisableAddin(AddinInfo addin)
        {
            var matchingAddin = (from x in db.CoreAddins
                                 where x.AddinGuid.Equals(addin.UniqueId)
                                 select x).FirstOrDefault();

            if (matchingAddin != null)
            {
                matchingAddin.AddinStatus = 1;
                addin.IsEnabled = false;
            }

            db.SubmitChanges();
        }

        public void EnableAddin(AddinInfo addin)
        {
            var matchingAddin = (from x in db.CoreAddins
                                 where x.AddinGuid.Equals(addin.UniqueId)
                                 select x).FirstOrDefault();

            if (matchingAddin != null)
            {
                matchingAddin.AddinStatus = 0;
                addin.IsEnabled = true;
            }

            db.SubmitChanges();
        }

        public void RemoveAddin(AddinInfo addin)
        {
            var matchingAddin = (from x in db.CoreAddins
                                 where x.AddinGuid.Equals(addin.UniqueId)
                                 select x).FirstOrDefault();

            if (matchingAddin != null)
            {
                matchingAddin.AddinStatus = 2;
                addin.IsEnabled = false;
            }

            db.SubmitChanges();
        }

        public void UnloadAllAddIns()
        {
            // Allow each addin to perform any cleanup first
            //TODO: Services must be stopped, UI elements unhosted, menu items removed, etc.
            foreach (AddinInfo addin in _loadedAddins)
            {
                if (addin.IsLoaded && addin.AddinInstance != null)
                {
                    addin.AddinInstance.OnUnload();
                }
            }

            // Remove from memory collection
            _loadedAddins.Clear();
        }
    }
}
